﻿/*
Copyright (c) 2008 William Duff

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace LiveUpload.YouTube
{
    /// <summary>
    /// Data related to this plugin that persists across sessions.
    /// </summary>
    internal class PersistentData
    {
        /// <summary>
        /// Constructs a new PersistentData object to encapsulate persistXml.
        /// </summary>
        /// <param name="persistXml">
        /// The persistXml file passed by the IPublishPlugin interface.
        /// </param>
        public PersistentData(XmlDocument persistXml)
        {
            if (persistXml == null)
            {
                throw new ArgumentNullException("persistXml");
            }
            this.persistXml = persistXml;

            this.settingsNode = this.persistXml.SelectSingleNode("Settings");
            if (this.settingsNode == null)
            {
                // The persistXml document must be blank, so initialize it.
                InitializeXml();
            }

            this.users = GetUsers();
            this.defaultUser = DefaultUserExists() ? GetDefaultUser() : null;

            // To embed the developer key and client id into the plugin, uncomment these lines:
            // this.developerKey = ""; // Add your developer key here.
            // this.clientId = ""; // Add your client id here.
            // And comment out these lines:
            this.developerKey = DeveloperKeyExists() ? GetDeveloperKey() : string.Empty;
            this.clientId = ClientIdExists() ? GetClientId() : string.Empty;
        }

        /// <summary>
        /// The persistXml file passed by the IPublishPlugin interface.
        /// </summary>
        private XmlDocument persistXml;

        /// <summary>
        /// Points to the Settings XmlNode inside persistXml.
        /// </summary>
        private XmlNode settingsNode;

        // Private variables.
        private List<Account> users;
        private Account defaultUser;
        private string developerKey;
        private string clientId;

        /// <summary>
        /// A unique identifier for the product required as part of the HTTP header to access 
        /// YouTube API features.
        /// </summary>
        public string DeveloperKey
        {
            get { return this.developerKey; }
            set { this.developerKey = value; }
        }

        /// <summary>
        /// An identifier for the product that is passed as a URL parameter to YouTube.
        /// </summary>
        public string ClientId
        {
            get { return this.clientId; }
            set { this.clientId = value; }
        }

        /// <summary>
        /// List of saved accounts.
        /// </summary>
        public List<Account> Users
        {
            get { return this.users; }
            set { this.users = value; }
        }

        /// <summary>
        /// The user that should be selected by default.
        /// </summary>
        public Account DefaultUser
        {
            get { return this.defaultUser; }
            set { this.defaultUser = value; }
        }

        /// <summary>
        /// Initializes the persistXml document to our own specification.
        /// </summary>
        private void InitializeXml()
        {
            this.persistXml.RemoveAll();

            XmlProcessingInstruction instruction = 
                this.persistXml.CreateProcessingInstruction("xml", "version=\"1.0\"");
            this.persistXml.AppendChild(instruction);

            this.settingsNode = this.persistXml.CreateElement("Settings");
            this.persistXml.AppendChild(this.settingsNode);

            XmlElement usersElement = this.persistXml.CreateElement("Users");
            this.settingsNode.AppendChild(usersElement);

            XmlElement defaultsElement = this.persistXml.CreateElement("Defaults");
            this.settingsNode.AppendChild(defaultsElement);
        }

        /// <summary>
        /// Gets an XmlElement, assuming it exists.
        /// </summary>
        /// <param name="elementName">
        /// The name of the element.
        /// </param>
        /// <param name="parentNode">
        /// The immediate parent of the element.
        /// </param>
        /// <returns>
        /// The specified XmlElement.
        /// </returns>
        private XmlElement GetXmlElement(string elementName, XmlNode parentNode)
        {
            if (elementName == null)
            {
                throw new ArgumentNullException("element");
            }
            if (parentNode == null)
            {
                throw new ArgumentNullException("parentNode");
            }

            XmlElement theElement = parentNode.SelectSingleNode(elementName) as XmlElement;
            if (theElement == null)
            {
                throw new ArgumentException(elementName + " does not exist.", "elementName");
            }
            return theElement;
        }

        /// <summary>
        /// Sets an XmlElement, replacing an existing XmlElement if necessary.
        /// </summary>
        /// <param name="element">
        /// The element to set.
        /// </param>
        /// <param name="parentNode">
        /// The immediate parent of the element.
        /// </param>
        private void SetXmlElement(XmlElement element, XmlNode parentNode)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
            if (parentNode == null)
            {
                throw new ArgumentNullException("parentNode");
            }

            XmlElement existingElement =
                parentNode.SelectSingleNode(element.LocalName) as XmlElement;
            if (existingElement == null)
            {
                parentNode.AppendChild(element);
            }
            else
            {
                parentNode.ReplaceChild(element, existingElement);
            }
        }

        /// <summary>
        /// Checks to see if a YouTube developer key is present.
        /// </summary>
        /// <returns>
        /// true if a developer key is present and false otherwise.
        /// </returns>
        private bool DeveloperKeyExists()
        {
            XmlElement developerKeyElement = 
                this.settingsNode.SelectSingleNode("DeveloperKey") as XmlElement;
            return developerKeyElement != null;
        }

        /// <summary>
        /// Gets the YouTube developer key.
        /// </summary>
        /// <returns>
        /// The YouTube developer key.
        /// </returns>
        private string GetDeveloperKey()
        {
            XmlElement developerKeyElement = GetXmlElement("DeveloperKey", this.settingsNode);
            return developerKeyElement.InnerText;
        }

        /// <summary>
        /// Sets the YouTube developer key to the specified string.
        /// </summary>
        /// <param name="developerKey">
        /// The developer key to set.
        /// </param>
        private void SetDeveloperKey(string developerKey)
        {
            if (developerKey == null)
            {
                throw new ArgumentNullException("developerKey");
            }

            XmlElement developerKeyElement = this.persistXml.CreateElement("DeveloperKey");
            developerKeyElement.InnerText = developerKey;

            SetXmlElement(developerKeyElement, this.settingsNode);
        }

        /// <summary>
        /// Checks to see if a YouTube client id is present.
        /// </summary>
        /// <returns>
        /// true if a client id is present and false otherwise.
        /// </returns>
        private bool ClientIdExists()
        {
            XmlElement clientIdElement =
                this.settingsNode.SelectSingleNode("ClientId") as XmlElement;
            return clientIdElement != null;
        }

        /// <summary>
        /// Gets the YouTube client id.
        /// </summary>
        /// <returns>
        /// The YouTube client id.
        /// </returns>
        private string GetClientId()
        {
            XmlElement clientIdElement = GetXmlElement("ClientId", this.settingsNode);
            return clientIdElement.InnerText;
        }

        /// <summary>
        /// Sets the YouTube client ID to the specified string.
        /// </summary>
        /// <param name="clientId">
        /// The client ID to set.
        /// </param>
        private void SetClientId(string clientId)
        {
            if (clientId == null)
            {
                throw new ArgumentNullException("clientId");
            }

            XmlElement clientIdElement = this.persistXml.CreateElement("ClientId");
            clientIdElement.InnerText = clientId;

            SetXmlElement(clientIdElement, this.settingsNode);
        }

        /// <summary>
        /// Gets a list of all user accounts.
        /// </summary>
        /// <returns>
        /// A list of user accounts.
        /// </returns>
        private List<Account> GetUsers()
        {
            XmlElement usersElement = GetXmlElement("Users", this.settingsNode);
            XmlNodeList userNodes = usersElement.SelectNodes("User");

            List<Account> users = new List<Account>();
            foreach (XmlElement userElement in userNodes)
            {
                string name = GetXmlElement("Name", userElement).InnerText;
                string token = GetXmlElement("Token", userElement).InnerText;
                users.Add(new Account(name, token));
            }

            return users;
        }

        /// <summary>
        /// Sets a list of user accounts.
        /// </summary>
        /// <param name="users">
        /// The list of user accounts to set.
        /// </param>
        private void SetUsers(List<Account> users)
        {
            if (users == null)
            {
                throw new ArgumentNullException("users");
            }

            XmlElement usersElement = GetXmlElement("Users", this.settingsNode);
            usersElement.RemoveAll();

            foreach (Account user in users)
            {
                XmlElement userElement = this.persistXml.CreateElement("User");

                XmlElement nameElement = this.persistXml.CreateElement("Name");
                nameElement.InnerText = user.Name;
                userElement.AppendChild(nameElement);

                XmlElement tokenElement = this.persistXml.CreateElement("Token");
                tokenElement.InnerText = user.Token;
                userElement.AppendChild(tokenElement);

                usersElement.AppendChild(userElement);
            }
        }

        /// <summary>
        /// Checks to see if a default user is present.
        /// </summary>
        /// <returns>
        /// true if a default user is present and false otherwise.
        /// </returns>
        private bool DefaultUserExists()
        {
            XmlElement defaultUserElement =
                this.settingsNode.SelectSingleNode("Defaults/User") as XmlElement;
            return defaultUserElement != null;
        }

        /// <summary>
        /// Gets the default user, assuming it exists.
        /// </summary>
        /// <returns>
        /// The default account.
        /// </returns>
        private Account GetDefaultUser()
        {
            XmlElement defaultsElement = GetXmlElement("Defaults", this.settingsNode);
            XmlElement defaultUserElement = GetXmlElement("User", defaultsElement);

            XmlElement defaultNameElement = GetXmlElement("Name", defaultUserElement);
            XmlElement defaultTokenElement = GetXmlElement("Token", defaultUserElement);

            return new Account(defaultNameElement.InnerText, defaultTokenElement.InnerText);
        }

        /// <summary>
        /// Sets a new default user.
        /// </summary>
        /// <param name="user">
        /// The user to set as default.
        /// </param>
        private void SetDefaultUser(Account user)
        {
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }

            XmlElement userElement = this.persistXml.CreateElement("User");

            XmlElement nameElement = this.persistXml.CreateElement("Name");
            nameElement.InnerText = user.Name;
            userElement.AppendChild(nameElement);

            XmlElement tokenElement = this.persistXml.CreateElement("Token");
            tokenElement.InnerText = user.Token;
            userElement.AppendChild(tokenElement);

            XmlElement defaultsElement = GetXmlElement("Defaults", this.settingsNode);
            SetXmlElement(userElement, defaultsElement);
        }

        /// <summary>
        /// Removes the default user, assuming it exists.
        /// </summary>
        private void RemoveDefaultUser()
        {
            XmlElement defaultsElement = GetXmlElement("Defaults", this.settingsNode);
            XmlElement defaultUserElement = GetXmlElement("User", defaultsElement);
            defaultsElement.RemoveChild(defaultUserElement);
        }

        /// <summary>
        /// Sets all of the public properties back into the XmlDocument.
        /// </summary>
        internal void Save()
        {
            SetDeveloperKey(this.developerKey);
            SetClientId(this.clientId);
            SetUsers(this.users);
            SetDefaultUser(this.defaultUser);
        }
    }
}
